package cn.itsource.ticket.runnable.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 *	1. 进程：就是一个应用程序，是一大堆代码
 *
 *	2. 线程：线程是进程中的最小单位，一小堆代码。线程是一个独立的功能。每一个线程都有自己独立的内存区域，例如，线程计数器。
 *			在当前进程中的所有线程，都会共享当前进程的共用内存区
 *
 *	3. 多线程作用：
 *			可以更好的利用硬件资源，更好的利用多核心优势，提高程序的执行效率。
 *
 *	4. 语法：
 *		Thread类，重写一个业务run方法,start方法启动线程后，每一个线程都是一个独立的功能，相互间不影响。
 *		实现线程方式两种：
 *			1. 继承Thread类
 *				步骤：
 *					1. 写一个线程类XxxThread extends Thread类
 *					2. 重写run()方法（该方法是业务方法，启动线程后，会自动执行该方法）
 *					3. 写一个测试类，在测试类中创建线程对象，调用对象的start()启动线程
 *
 *			2. 实现Runnable接口
 *				步骤：
 *					1. 写一个实现业务类XxxRunnableImpl implements Runnable接口
 *					2. 重写run()方法（该方法是业务方法，启动线程后，会自动执行该方法）
 *					3. 写一个测试类，在测试类中
 *						先创建业务类对象
 *						再创建线程对象，并且将业务类对象作为参数传入线程的构造方法中，调用线程对象的start()启动线程
 *
 *		以后都用实现的方式，因为Java中单继承多实现，实现的扩展性强
 *
 *	5. Thread中常用方法：
 *			1. 构造方法：
 *					Thread() 创建一个新的 Thread对象。  
					Thread(String name) 创建一个新的有指定线程名的 Thread对象。  
					Thread(Runnable target)  将Runnable业务类对象作为参数，创建一个新的 Thread对象。    
					Thread(Runnable target, String name) 将Runnable业务类对象作为参数，创建一个新的有指定线程名的 Thread对象。  
 *
 *			2. 普通方法：
 *					1. static Thread currentThread() 返回对当前正在执行的线程对象的引用。  
					2. String getName() 返回此线程的名称。 
					3. void setName(String name) 设置线程对象的名字   
					4. void setPriority(int newPriority)   设置线程对象的优先级
					5. int getPriority()  获取线程的优先级
						1-10，越小优先级越高，执行越早，越大就执行越晚
						5是默认优先级。
						当前线程的优先级与创建线程对象的环境的优先级一致
						
					6. boolean isDaemon() 判断这个线程是否是守护线程。  GC是守护线程
						一个线程的默认状态和创建它的环境线程状态一致
					
					7. void setDaemon(boolean on) 将当前线程设置为守护线程   
						活跃的线程不能设置为守护线程
						
					8. void join() 等待join线程执行完后，再执行当前线程
					9. void join(long millis)等待join线程线程死亡最多 millis毫秒。  
					10. static void sleep(long millis)  阻塞当前线程指定的毫秒数后再执行
						一般用来模拟网络延迟，可以做计时器，还可以做倒计时
						
						以下在在多线程中常用
					11. void interrupt() 中断这个线程。  在多线程中常用
					12. static boolean interrupted() 测试当前线程是否中断。  在多线程中常用
					13. boolean isAlive() 测试这个线程是否活着。 
					14. Thread.State getState() 返回此线程的状态。  
					15. long getId() 返回此线程的标识符。  		
					
	6. 线程同步方式：
		6.1 为什么？为了保证业务逻辑合理性
		6.2 三种方式：
				一、悲观锁：考虑的最坏的情况，每一次都会有线程安全问题。效率低下
					1. 同步代码块
						语法：
						synchronized(同步对象){//必须保证：几个有安全问题的线程对象看到的都是统一对象
							有线程安全问题代码
						}
						
					2. 同步方法
						语法：直接在方法的修饰符位置写上 synchronized在返回值前面
						如果方法是static修饰的 ：    同步的是
						如果方法是非static修饰的：同步的是
				
				二、乐观锁：考虑的是最好的情况，没有安全问题。一旦碰到线程安全问题，才启用锁机制。底层使用的是自旋机制
				3. 锁机制 Lock接口的实现类ReentrantLock
						构造方法：
							1. ReentrantLock() 创建一个 ReentrantLock的实例。   不要公平机制。效率高
							2. ReentrantLock(boolean fair) 根据给定的公平政策创建一个 ReentrantLock的实例。  理论上获取锁的几率是相同的
						 
						 class X {
						   private final ReentrantLock lock = new ReentrantLock();
						
						   public void m() { 
						     lock.lock();  // 上锁
						     try {
						       	//有线程安全问题的代码
						     } finally {
						       lock.unlock();//释放锁
						     }
						   }
						 }
				
		6.3 同步对象：
			什么时候可以保证只有一个对象的情况？
			1. static 静态区，只有一份
			2. 字节码文件只有一份在元空间
			
		6.4 注意：
			1. 必须保证几个线程对象看到的是同一个同步对象（一般使用当前类的字节码文件）
			2. 线程同步的原则：最小范围原则，为了保证执行效率
			
		6.5 线程同步的使用选择
			一般我们用乐观锁机制，因为效率高，且更灵活，功能更强大。
			如果用悲观锁，建议使用同步代码块因为效率高。
			但是，如果对效率没有要求，就用同步方法，因为最简单
		
 * 卖票线程业务类
 */
public class TicketRunnableImpl implements Runnable{
	/**50张票，因为3个线程共享同一个业务类对象，所以不用static修饰*/
	private int ticket = 50;
	/**同步锁对象，因为只有一个业务类对象，所有不用static修饰*/
	private final ReentrantLock lock = new ReentrantLock();
	
	@Override
	public void run() {
		//卖票
		while (ticket > 0) {//只要有票就卖
			//调用卖一张票方法
			saleOne();//调用方和被调用方都没有static修饰，都在同一个类中，可以直接方法名调用
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 卖一张票方法
	 */
	private void saleOne(){
		//获取当前线程对象
		Thread currentThread = Thread.currentThread();
		//获取当前线程对象的名字
		String name = currentThread.getName();
		lock.lock();//上锁
		try {
			//再次判断是否有余票
			if (ticket > 0) {
				//表示卖的票号
				System.out.println(name + "---当前票号：" + ticket);
				//卖一张减一张
				ticket--;
			}
		} finally {
			lock.unlock();//释放锁
		}
	}
}
